package net.sf.xoperator.configuration;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;

import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;

import net.sf.xoperator.ui.UIAimlConfiguration;
import net.sf.xoperator.ui.aiml.AIMLTemplate;
import net.sf.xoperator.ui.trans.SPARQL2TextConfig;
import net.sf.xoperator.xmpp.AccountInfo;

public class SerialisingPersister {

	public static final String CONFIG_FILE_NAME = "agentConf.ser";

	public static final String TRANSFORM_FILE_NAME = "transform.xsl";

	public static final String FILE_SEPARATOR = System
			.getProperty("file.separator");

	private static final String AIML_FOLDER = "aiml";

	private static final String TEMPLATE_FOLDER = "templates";

	private static final String CONTEXT_FILE_NAME = "context.xml";

	private static final String PROPERTIES_FILE_NAME = "properties.xml";

	private static final String SPLITTERS_FILE_NAME = "splitters.xml";

	private static final String SUBSTITUIONS_FILE_NAME = "substitutions.xml";

	private static final String DEFAULT_TEMPLATE_NAME = "default.aiml";

	private static final String USER_TEMPLATE_NAME = "user.aiml";

	private static final String LOG_FOLDER_NAME = "logs";

	private static final String CONF_FOLDER_IN_CLASSPATH = "net/sf/xoperator/ui/aiml/conf/";

	private static final String GROOVY_SAMPLE = "sample.groovy";
	private static final String FOAF_GROUP_ATT_SAMPLE = "foafGroupAttributes.groovy";
	private static final String FOAF_GROUP_MEM_SAMPLE = "foafGroupMember.groovy";
	private static final String FOAF_PERSON_ATT_SAMPLE = "foafPersonAttributes.groovy";
	private static final String ICAL_SAMPLE = "iCalWhereNow.groovy";
	private static final String TELL_ME_MORE_SAMPLE = "tellMeMoreAbout.groovy";


	private Log log = LogFactory.getLog(SerialisingPersister.class);

	public SerialisingPersister(String username) {
		this.userFolder = new File(usernameToFoldername(username));

	}

	private File userFolder = null;

	public Map createUserFolder(AccountInfo main, AccountInfo proxy) {
		try {
			// create the userfolder
			this.userFolder.mkdir();
			createFile(this.userFolder.getPath() + FILE_SEPARATOR
					+ TRANSFORM_FILE_NAME,
					"net/sf/xoperator/ui/trans/transformation.xsl");

			// create the aiml data
			createAmilFolder();

			// populate the folder with the data

			Map<String, PersistentKSConfiguration> configs = new HashMap<String, PersistentKSConfiguration>();

			configs.put(main.getClass().getCanonicalName() + main.getName(),
					main);
			if(proxy!=null){
				configs.put(proxy.getClass().getCanonicalName() + proxy.getName(),
						proxy);
			}
			

			// create an intial aiml configuration object
			UIAimlConfiguration aiconf = new UIAimlConfiguration();
			aiconf.setPath(this.userFolder.getAbsolutePath() + FILE_SEPARATOR
					+ AIML_FOLDER + FILE_SEPARATOR + PROPERTIES_FILE_NAME);
			configs.put(
					aiconf.getClass().getCanonicalName() + aiconf.getName(),
					aiconf);

			// create a transformer config, that points to the fresh one just
			// created
			SPARQL2TextConfig s2tconfig = new SPARQL2TextConfig();

			s2tconfig.setStyleSheet(new File(this.userFolder.getAbsoluteFile()
					+ FILE_SEPARATOR + TRANSFORM_FILE_NAME));

			configs.put(s2tconfig.getClass().getCanonicalName()
					+ s2tconfig.getName(), s2tconfig);

			saveConfiguration(configs);

			return configs;

		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private void createAmilFolder() throws IOException {
		// create Base Folder
		File aimlFolder = new File(this.userFolder.getAbsolutePath()
				+ FILE_SEPARATOR + AIML_FOLDER);
		aimlFolder.mkdir();

		// flush the conf files into the Folder
		createFile(aimlFolder.getAbsolutePath() + FILE_SEPARATOR
				+ CONTEXT_FILE_NAME, CONF_FOLDER_IN_CLASSPATH
				+ CONTEXT_FILE_NAME);
		createFile(aimlFolder.getAbsolutePath() + FILE_SEPARATOR
				+ PROPERTIES_FILE_NAME, CONF_FOLDER_IN_CLASSPATH
				+ PROPERTIES_FILE_NAME);
		createFile(aimlFolder.getAbsolutePath() + FILE_SEPARATOR
				+ SPLITTERS_FILE_NAME, CONF_FOLDER_IN_CLASSPATH
				+ SPLITTERS_FILE_NAME);
		createFile(aimlFolder.getAbsolutePath() + FILE_SEPARATOR
				+ SUBSTITUIONS_FILE_NAME, CONF_FOLDER_IN_CLASSPATH
				+ SUBSTITUIONS_FILE_NAME);

		// create the log folder
		File logFolder = new File(aimlFolder.getAbsolutePath() + FILE_SEPARATOR
				+ LOG_FOLDER_NAME);
		logFolder.mkdir();
		// create template Folder
		File templateFolder = new File(aimlFolder.getAbsolutePath()
				+ FILE_SEPARATOR + TEMPLATE_FOLDER);
		templateFolder.mkdir();
		createFile(templateFolder.getAbsolutePath() + FILE_SEPARATOR
				+ DEFAULT_TEMPLATE_NAME, CONF_FOLDER_IN_CLASSPATH
				+ TEMPLATE_FOLDER + "/" + DEFAULT_TEMPLATE_NAME);
		createFile(templateFolder.getAbsolutePath() + FILE_SEPARATOR
				+ USER_TEMPLATE_NAME, CONF_FOLDER_IN_CLASSPATH
				+ TEMPLATE_FOLDER + "/" + USER_TEMPLATE_NAME);
		createFile(templateFolder.getAbsolutePath() + FILE_SEPARATOR
				+ GROOVY_SAMPLE, CONF_FOLDER_IN_CLASSPATH
				+ TEMPLATE_FOLDER + "/" + GROOVY_SAMPLE);
		createFile(templateFolder.getAbsolutePath() + FILE_SEPARATOR
				+ FOAF_GROUP_ATT_SAMPLE, CONF_FOLDER_IN_CLASSPATH
				+ TEMPLATE_FOLDER + "/" + FOAF_GROUP_ATT_SAMPLE);
		createFile(templateFolder.getAbsolutePath() + FILE_SEPARATOR
				+ FOAF_GROUP_MEM_SAMPLE, CONF_FOLDER_IN_CLASSPATH
				+ TEMPLATE_FOLDER + "/" + FOAF_GROUP_MEM_SAMPLE);
		createFile(templateFolder.getAbsolutePath() + FILE_SEPARATOR
				+ FOAF_PERSON_ATT_SAMPLE, CONF_FOLDER_IN_CLASSPATH
				+ TEMPLATE_FOLDER + "/" + FOAF_PERSON_ATT_SAMPLE);
		createFile(templateFolder.getAbsolutePath() + FILE_SEPARATOR
				+ ICAL_SAMPLE, CONF_FOLDER_IN_CLASSPATH
				+ TEMPLATE_FOLDER + "/" + ICAL_SAMPLE);
		createFile(templateFolder.getAbsolutePath() + FILE_SEPARATOR
				+ TELL_ME_MORE_SAMPLE, CONF_FOLDER_IN_CLASSPATH
				+ TEMPLATE_FOLDER + "/" + TELL_ME_MORE_SAMPLE);

	}

	public Map<String, KSConfiguration> loadConfiguration(AccountInfo main,
			AccountInfo proxy) {
		Map configs = loadConfiguration();

		// not possible to load data, so theres nothing to merge and we can
		// simply exit
		if (configs != null) {
			// if a password is provided for the startup, update the loaded data
			if (main.validate()) {
				configs.put(AccountInfo.class.getCanonicalName()
						+ AccountInfo.MAIN, main);
			}
			// same with the proxy

			if (proxy!=null && proxy.validate()) {
				configs.put(AccountInfo.class.getCanonicalName()
						+ AccountInfo.PROXY, proxy);
			}
//			add the aiml templates
			for (AIMLTemplate template : loadAIMLTemplates()) {
				configs.put(template.getName(), template);
			}
			

		}
		
		return configs;

	}

	public Map<String, PersistentKSConfiguration> loadConfiguration() {
		// we can save all the hassle if the folder is not yet created

		if (!this.userFolder.exists()) {
			return null;
		}
		try {

			ObjectInputStream ois = new ObjectInputStream(
					new BufferedInputStream(new FileInputStream(this.userFolder
							.getAbsolutePath()
							+ FILE_SEPARATOR + CONFIG_FILE_NAME)));
			Map<String, PersistentKSConfiguration> configs = (Map<String, PersistentKSConfiguration>) ois
					.readObject();
			ois.close();
			return configs;

		} catch (Exception e) {
			this.log.error("Error loading a previuosly made config", e);
			return null;
		}
	}

	/**
	 * saves the configuration of the agent in two ways. Serializable data is
	 * serialized, AIML templates are written into the xml file.
	 * 
	 * @param configs
	 */
	public void saveConfiguration(Map<String, PersistentKSConfiguration> configs) {

		try {
			ObjectOutputStream ous = new ObjectOutputStream(
					new BufferedOutputStream(new FileOutputStream(
							this.userFolder.getAbsolutePath() + FILE_SEPARATOR
									+ CONFIG_FILE_NAME)));
			ous.writeObject(configs);
			ous.close();
		} catch (Exception e) {
			this.log.error("Error while saving the configuration", e);
		}

	}

	private String inputStreamAsString(InputStream stream) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(stream));
		StringBuilder sb = new StringBuilder();
		String line = null;

		while ((line = br.readLine()) != null) {
			sb.append(line + "\n");
		}

		br.close();
		return sb.toString();
	}

	private String usernameToFoldername(String username) {
		return username.replace("@", "_at_").replace(".", "_dot_");
	}

	public File getUserFolder() {
		return this.userFolder;
	}

	private void createFile(String location, String sourceLocation)
			throws IOException {
		File file = new File(location);
		FileWriter writer = new FileWriter(file);
      log.debug("Creating file: " + sourceLocation);
		writer.write(inputStreamAsString(ClassLoader
				.getSystemResourceAsStream(sourceLocation)));

		writer.close();
	}

	public boolean saveAimlConfig(Map<String, AIMLTemplate> aimlconf) {
		boolean change = false;
		// parse the XML file
		try {
			String filepath = userFolder.getAbsolutePath() + FILE_SEPARATOR
					+ AIML_FOLDER + FILE_SEPARATOR + TEMPLATE_FOLDER
					+ FILE_SEPARATOR + USER_TEMPLATE_NAME;
//			Document doc = new SAXBuilder().build(filepath);
			Document doc = new Document();
			doc.setRootElement(new Element("aiml"));
			for (AIMLTemplate template : aimlconf.values()) {
//				if (!template.isPersistet()) {

//					if (template.isObsolete()) {
//						// delete it
//						Element todelete = locateCategoryElementByPattern(doc,
//								template.getPattern());
//						if (todelete != null) {
//							todelete.detach();
//						}
//
//					}
					if (!template.isObsolete()) {
						// delete it
//						Element toupdate = locateCategoryElementByPattern(doc,
//								template.getPattern());
//						if (toupdate != null) {
//							toupdate.detach();
//						}
						// add a new one
						Element cat = new Element("category");
						cat.addContent(new Element("pattern")
								.addContent(template.getPattern()));
						Element ext = new Element("external");
						
						ext.setAttribute("name","query");
						cat.addContent(new Element("template") //.addContent("The result of your query is: ")
								.addContent(ext));
						ext.setAttribute("param", template.getQuery());
						
						doc.getRootElement().addContent(cat);

					}
					// write it
					
					change = true;
					template.setPersistet(true);
//				}
			}
			new XMLOutputter(Format.getPrettyFormat()).output(doc,
					new FileOutputStream(filepath));
		}  catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return change;

	}

	public List<AIMLTemplate> loadAIMLTemplates() {
		List<AIMLTemplate> templates = null;
		String filepath = userFolder.getAbsolutePath() + FILE_SEPARATOR
				+ AIML_FOLDER + FILE_SEPARATOR + TEMPLATE_FOLDER
				+ FILE_SEPARATOR + USER_TEMPLATE_NAME;
		Document doc;
		try {
			doc = new SAXBuilder().build(filepath);
			templates = new ArrayList<AIMLTemplate>();

			for (Object templobj : doc.getRootElement().getChildren()) {
				Element cat = (Element) templobj;
				AIMLTemplate aiml = new AIMLTemplate();
				aiml.setPattern(cat.getChildText("pattern"));
				aiml.setQuery(cat.getChild("template").getChild("external")
						.getAttributeValue("param"));
				aiml.setPersistet(true);
				templates.add(aiml);

			}
			
		} catch (JDOMException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return templates;
	}

	private Element locateCategoryElementByPattern(Document doc, String pattern)
			throws JDOMException {
		return (Element) XPath.selectSingleNode(doc,
				"/aiml/category/pattern[text() = \"" + pattern + "\"]/../..");

	}

}
